#include"Painter.h"
#include <QPaintEvent>
#include <QPainter>
#include <QDebug>
#include <cmath>
#include <QPainterPath>

Painter::Painter(QWidget *parent)
    : QFrame(parent),m_bottom(0),m_left(0),m_top(0),m_right(0),//绘图区域的边界，单位为像素。默认值为0，表示绘图区域覆盖整个QWidget，后面会赋值
      m_Ymin(-10),m_Ymax(10),m_Xmin(-10),m_Xmax(10)//初始化XY轴的范围
{
    myList =&static_cast<Plan*>(parent)->myList;//获取函数列表中的绘图信息
    setMouseTracking(true);//在鼠标移动时捕获鼠标事件信号
    canDrawXy=true;
    canDrawGrid=true;//默认网格、坐标轴二者都画
}

void Painter::resizeEvent(QResizeEvent *event)//绘制图纸大小调整事件
{
    //确保绘图区域在 Painter 对象的中心位置，并且边界始终与 Painter 对象的边界保持一定的距离
    //static_cast<int>用于将浮点数转换成整数
    m_top= static_cast<int>(round(rect().bottom()*0.037));
    m_bottom= static_cast<int>(round(rect().bottom()*0.975));
    m_left= static_cast<int>(round(rect().right()*0.057));
    m_right= static_cast<int>(round(rect().right()*0.958));
    //设置绘图区域的上下左右四个边界位置
    //更新 m_top、m_bottom、m_left 和 m_right 四个成员变量，以反映新的绘图区域边界
}

//首先绘制坐标轴，更具设备坐标画
void Painter::drawAxis(QPainter &painter)
{
    painter.setPen(QPen(Qt::blue,2.0,Qt::SolidLine,Qt::RoundCap,Qt::MiterJoin));//设置画笔颜色和线宽，以及线条的样式和端点的形状
    painter.setFont(QFont("微软雅黑",15));//设置字体大小和类型
    //确定原点
    int Ox= LxtoDx(0);
    int Oy= LytoDy(0);
    if(Oy>=m_top&&Oy<=m_bottom)//判断是否在top和botton之间
    {
        //x方向
        painter.drawLine(m_left,Oy,m_right+10,Oy);
        painter.drawText(m_right+10,Oy+10,"X");
        //箭头
        painter.drawLine(m_right+10,Oy,m_right-5,Oy+5);
        painter.drawLine(m_right+10,Oy,m_right-5,Oy-5);
    }

    if(Ox>=m_left&&Ox<=m_right)
    {
        //y方向
        painter.drawLine(Ox,m_top-10,Ox,m_bottom);
        painter.drawText(Ox+10,m_top-10,"Y");
        //箭头
        painter.drawLine(Ox,m_top-12,Ox+5,m_top);
        painter.drawLine(Ox,m_top-12,Ox-5,m_top);
    }
}

//接下来绘制网格，也根据设备坐标画
void Painter::drawGrid(QPainter &painter)
{
    painter.setPen(QPen(Qt::darkGray,1.0,Qt::DashDotLine));//设置画笔颜色、线宽和线条的样式为虚线
    //x 方向
    for(int i=m_left;i<m_right;i+=50)
    {
        painter.drawLine(i,m_bottom,i,m_top);
    }
    //y 方向
    for(int i=m_bottom;i>m_top;i-=50)
    {
        painter.drawLine(m_left,i,m_right,i);
    }
}

//再绘制刻度，刻度根据设备坐标画，但是数值是通过后面四个函数利用逻辑坐标画的
void Painter::drawScale(QPainter &painter)
{
    //创建画笔对象
    painter.setPen(QPen(Qt::darkGray,1.0,Qt::DashDotLine));//颜色，线宽，线形
    painter.setFont(QFont("微软雅黑",10));//绘制刻度值的文本

    //绘制 x 方向的刻度线，不是坐标轴
    painter.drawLine(m_left,m_bottom,m_right,m_bottom);
    //每隔 50 个像素绘制一条垂直线段和一个刻度值
    for(int i=m_left;i<m_right;i+=50)
    {
        painter.drawLine(i,m_bottom,i,m_bottom+3);//刻度线
        painter.drawText(i-20,m_bottom+20,QString::asprintf("%.2f",DxtoLx(i)));//刻度值
    }
    //y 方向同理
    painter.drawLine(m_left,m_top,m_left,m_bottom);
    for(int i=m_bottom;i>m_top;i-=50)
    {
        painter.drawLine(m_left-5,i,m_left-2,i);
        painter.drawText(m_left-55,i+1,QString::asprintf("%.2f",DytoLy(i)));
    }
}

//接下来要绘制函数图像了
void Painter::drawFunc(QPainter &painter)
{
    for(auto& item:myList->funcList)//遍历列表
    {
        if(item->canDraw)//检查是否显示
        {
            QPen pen(item->mycolor.c_str());
            pen.setStyle(static_cast< Qt::PenStyle>(item->mystyle));
            painter.setPen(pen);
            bool shouldMovepen=true;//确保曲线在不可见区域内不被绘制，而只在可见区域内绘制
            QPainterPath path;
            for(auto& i:item->mypoint)//遍历该函数所存的坐标
            {
                int x=LxtoDx(i.first);//pair中第一个数，即横坐标
                int y=LytoDy(i.second);//转化为设备坐标
                if(shouldMovepen)//是否移动起点
                {
                    if(x>m_left&&x<m_right&&y>m_top&&y<m_bottom)
                    {
                        shouldMovepen= false;
                        path.moveTo(x,y);//将起点移动到这
                    }
                    else
                        shouldMovepen=true;
                }
                else if(x>m_left&&x<m_right&&y>m_top&&y<m_bottom)//不需要移动起点，直接连线
                {
                    path.lineTo(x,y);
                }
                else
                    shouldMovepen=true;//不在可见范围，需要移动起点
            }
            //抗锯齿，效果更好
            painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing);
            painter.drawPath(path);//画一条由path决定的路径
        }
    }
}

void Painter::paintEvent(QPaintEvent *event)//绘制开关，不需要手动调用，将由Qt框架自动在需要时调用
{
    QPainter painter(this);
    if(canDrawXy)
        drawAxis(painter);
    if(canDrawGrid)
        drawGrid(painter);
    drawScale(painter);
    drawFunc(painter);
}

//这四个函数用于将逻辑坐标系中的坐标值转换为屏幕像素坐标值，或者将屏幕像素坐标值转换为逻辑坐标系中的坐标值。
int Painter::LxtoDx(double x) const
{
    return static_cast<int>(m_left+(x-m_Xmin)*(m_right-m_left)/(m_Xmax-m_Xmin));
}
//首先，计算逻辑坐标系中x所占的比例，即(x-m_Xmin)/(m_Xmax-m_Xmin)
//然后，将这个比例乘以屏幕的宽度(m_right-m_left)，得到x在屏幕中所占的宽度。
//最后，将上述宽度加上屏幕的左端点坐标(m_left)，得到x在屏幕中的横坐标值。
//下面三个类似，我就不加赘述了
double Painter::DxtoLx(int x) const
{
    return m_Xmin+(x-m_left)*(m_Xmax-m_Xmin)/(m_right-m_left);
}

int Painter::LytoDy(double y) const
{
    return static_cast<int>(m_bottom - (y-m_Ymin)* (m_bottom-m_top) /(m_Ymax-m_Ymin));
}

double Painter::DytoLy(int y) const
{
    return m_Ymin+(m_bottom-y)*(m_Ymax-m_Ymin)/(m_bottom-m_top);
}
